import 'package:sqliteasy_core/enums/null_value_strategy.dart';

class Database {
  /// The list of entities included in the database. Each entity turns into a table in the
  /// database.
  ///
  /// @return The list of entities in the database.
  final List<Type> entities;

  /// The list of database views included in the database. Each class turns into a view in the
  /// database.
  ///
  /// @return The list of database views.
  final List<Type> views;

  /// The database version.
  ///
  /// @return The database version.
  final int version;

  /// You can set the annotation processor argument ({@code room.schemaLocation}) to tell Room to
  /// export the database schema into a folder. Even though it is not mandatory, it is a good
  /// practice to have version history of your schema in your codebase and you should commit the
  /// schema files into your version control system (but don't ship them with your app!).
  /// <p>
  /// When {@code room.schemaLocation} is set, Room will check this variable and if it is set to
  /// {@code true}, the database schema will be exported into the given folder.
  /// <p>
  /// {@code exportSchema} is {@code true} by default but you can disable it for databases when
  /// you don't want to keep history of versions (like an in-memory only database).
  ///
  /// @return Whether the schema should be exported to the given folder when the
  /// {@code room.schemaLocation} argument is set. Defaults to {@code true}.
  final bool exportSchema;

  final NullValueStrategy nullValueStrategy;

  const Database(this.entities, this.version, {this.exportSchema, this.views, this.nullValueStrategy = NullValueStrategy.apply});

}

class Entity {
  /// The table name in the SQLite database. If not set, defaults to the class name.
  ///
  /// @return The SQLite tableName of the Entity.
  final String tableName;

  /// List of indices on the table.
  ///
  /// @return The list of indices on the table.
  final List<Index> indices;

  /// If set to {@code true}, any Index defined in parent classes of this class will be carried
  /// over to the current {@code Entity}. Note that if you set this to {@code true}, even if the
  /// {@code Entity} has a parent which sets this value to {@code false}, the {@code Entity} will
  /// still inherit indices from it and its parents.
  /// <p>
  /// When the {@code Entity} inherits an index from the parent, it is <b>always</b> renamed with
  /// the default naming schema since SQLite <b>does not</b> allow using the same index name in
  /// multiple tables. See {@link Index} for the details of the default name.
  /// <p>
  /// By default, indices defined in parent classes are dropped to avoid unexpected indices.
  /// When this happens, you will receive a {@link RoomWarnings#INDEX_FROM_PARENT_FIELD_IS_DROPPED}
  /// or {@link RoomWarnings#INDEX_FROM_PARENT_IS_DROPPED} warning during compilation.
  ///
  /// @return True if indices from parent classes should be automatically inherited by this Entity,
  ///         false otherwise. Defaults to false.
  final bool inheritSuperIndices;

  /// The list of Primary Key column names.
  /// <p>
  /// If you would like to define an auto generated primary key, you can use {@link PrimaryKey}
  /// annotation on the field with {@link PrimaryKey#autoGenerate()} set to {@code true}.
  ///
  /// @return The primary key of this Entity. Can be empty if the class has a field annotated
  /// with {@link PrimaryKey}.
  final List<String> primaryKeys;

  /// List of {@link ForeignKey} constraints on this entity.
  ///
  /// @return The list of {@link ForeignKey} constraints on this entity.
  final List<ForeignKey> foreignKeys;

  /// The list of column names that should be ignored by Room.
  /// <p>
  /// Normally, you can use {@link Ignore}, but this is useful for ignoring fields inherited from
  /// parents.
  /// <p>
  /// Columns that are part of an {@link Embedded} field can not be individually ignored. To ignore
  /// columns from an inherited {@link Embedded} field, use the name of the field.
  ///
  /// @return The list of field names.
  final List<String> ignoredColumns;

  const Entity({this.tableName,
      this.indices,
      this.foreignKeys,
      this.ignoredColumns,
      this.inheritSuperIndices,
      this.primaryKeys});
}

class Column {
  /// The column name.
  final String name;
  /// The column type affinity.
  final String type;
  /// The column type after it is normalized to one of the basic types according to
  /// https://www.sqlite.org/datatype3.html Section 3.1.
  /// <p>
  /// This is the value Room uses for equality check.
  final int affinity;
  /// Whether or not the column can be NULL.
  final bool notNull;
  /// The position of the column in the list of primary keys, 0 if the column is not part
  /// of the primary key.
  /// <p>
  /// This information is only available in API 20+.
  /// <a href="https://www.sqlite.org/releaselog/3_7_16_2.html">(SQLite version 3.7.16.2)</a>
  /// On older platforms, it will be 1 if the column is part of the primary key and 0
  /// otherwise.
  /// <p>
  /// The {@link #equals(Object)} implementation handles this inconsistency based on
  /// API levels os if you are using a custom SQLite deployment, it may return false
  /// positives.
  final int primaryKeyPosition;
  /// The default value of this column.
  final String defaultValue;

  const Column({this.name, this.type, this.affinity, this.notNull,
      this.primaryKeyPosition, this.defaultValue});
}

class Index {
  /// List of column names in the Index.
  /// <p>
  /// The order of columns is important as it defines when SQLite can use a particular index.
  /// See <a href="https://www.sqlite.org/optoverview.html">SQLite documentation</a> for details on
  /// index usage in the query optimizer.
  ///
  /// @return The list of column names in the Index.
  final List<String> columns;

  /// Name of the index. If not set, Room will set it to the list of columns joined by '_' and
  /// prefixed by "index_${tableName}". So if you have a table with name "Foo" and with an index
  /// of {"bar", "baz"}, generated index name will be  "index_Foo_bar_baz". If you need to specify
  /// the index in a query, you should never rely on this name, instead, specify a name for your
  /// index.
  ///
  /// @return The name of the index.
  final String name;

  /// If set to true, this will be a unique index and any duplicates will be rejected.
  ///
  /// @return True if index is unique. False by default.
  final bool unique;

  const Index(this.columns, {this.name, this.unique});
}

class ForeignKey {
  /// The parent Entity to reference. It must be a class annotated with {@link Entity} and
  /// referenced in the same database.
  ///
  /// @return The parent Entity.
  final Type entity;

  /// The list of column names in the parent {@link Entity}.
  /// <p>
  /// Number of columns must match the number of columns specified in {@link #childColumns()}.
  ///
  /// @return The list of column names in the parent Entity.
  /// @see #childColumns()
  final List<String> referenceColumns;

  /// The list of column names in the current {@link Entity}.
  /// <p>
  /// Number of columns must match the number of columns specified in {@link #parentColumns()}.
  ///
  /// @return The list of column names in the current Entity.
  final List<String> columns;

  /// Action to take when the parent {@link Entity} is deleted from the database.
  /// <p>
  /// By default, {@link #NO_ACTION} is used.
  ///
  /// @return The action to take when the referenced entity is deleted from the database.
  final Action onDelete;

  /// Action to take when the parent {@link Entity} is updated in the database.
  /// <p>
  /// By default, {@link #NO_ACTION} is used.
  ///
  /// @return The action to take when the referenced entity is updated in the database.
  final Action onUpdate;

  /// * A foreign key constraint can be deferred until the transaction is complete. This is useful
  /// if you are doing bulk inserts into the database in a single transaction. By default, foreign
  /// key constraints are immediate but you can change it by setting this field to {@code true}.
  /// You can also use
  /// <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a>
  /// PRAGMA to defer them depending on your transaction.
  ///
  /// @return Whether the foreign key constraint should be deferred until the transaction is
  /// complete. Defaults to {@code false}.
  final bool deferred;

  const ForeignKey({this.entity, this.columns, this.referenceColumns,
      this.deferred, this.onDelete, this.onUpdate});
}

/// Constants definition for values that can be used in {@link #onDelete()} and
/// {@link #onUpdate()}.
enum Action {
  /// Possible value for {@link #onDelete()} or {@link #onUpdate()}.
  /// <p>
  /// When a parent key is modified or deleted from the database, no special action is taken.
  /// This means that SQLite will not make any effort to fix the constraint failure, instead,
  /// reject the change.
  NO_ACTION,

  /// Possible value for {@link #onDelete()} or {@link #onUpdate()}.
  /// <p>
  /// The RESTRICT action means that the application is prohibited from deleting
  /// (for {@link #onDelete()}) or modifying (for {@link #onUpdate()}) a parent key when there
  /// exists one or more child keys mapped to it. The difference between the effect of a RESTRICT
  /// action and normal foreign key constraint enforcement is that the RESTRICT action processing
  /// happens as soon as the field is updated - not at the end of the current statement as it would
  /// with an immediate constraint, or at the end of the current transaction as it would with a
  /// {@link #deferred()} constraint.
  /// <p>
  /// Even if the foreign key constraint it is attached to is {@link #deferred()}, configuring a
  /// RESTRICT action causes SQLite to return an error immediately if a parent key with dependent
  /// child keys is deleted or modified.
  RESTRICT,

  /// Possible value for {@link #onDelete()} or {@link #onUpdate()}.
  /// <p>
  /// If the configured action is "SET NULL", then when a parent key is deleted
  /// (for {@link #onDelete()}) or modified (for {@link #onUpdate()}), the child key columns of all
  /// rows in the child table that mapped to the parent key are set to contain {@code NULL} values.
  SET_NULL,

  /// Possible value for {@link #onDelete()} or {@link #onUpdate()}.
  /// <p>
  /// The "SET DEFAULT" actions are similar to {@link #SET_NULL}, except that each of the child key
  /// columns is set to contain the columns default value instead of {@code NULL}.
  SET_DEFAULT,

  /// Possible value for {@link #onDelete()} or {@link #onUpdate()}.
  /// <p>
  /// A "CASCADE" action propagates the delete or update operation on the parent key to each
  /// dependent child key. For {@link #onDelete()} action, this means that each row in the child
  /// entity that was associated with the deleted parent row is also deleted. For an
  /// {@link #onUpdate()} action, it means that the values stored in each dependent child key are
  /// modified to match the new parent key values.
  CASCADE,
}

class Actions {
  static const NO_ACTION = "NO ACTION";
  static const RESTRICT = "RESTRICT";
  static const SET_NULL = "SET NULL";
  static const SET_DEFAULT = "SET DEFAULT";
  static const CASCADE = "CASCADE";

  static Action getFromName(String name) {
    switch(name) {
      case NO_ACTION:
        return Action.NO_ACTION;
      case RESTRICT:
        return Action.RESTRICT;
      case SET_NULL:
        return Action.SET_NULL;
      case SET_DEFAULT:
        return Action.SET_DEFAULT;
      case CASCADE:
        return Action.CASCADE;
      default:
        throw Exception("unsupported action name ${name}");
    }
  }
}

/// To mark a column ignored.
class Ignore {
  const Ignore();
}

class View {
  //TODO: Implement
}

class PrimaryKey {
  final bool autoGenerate;
  final int position;
  const PrimaryKey({this.autoGenerate, this.position});
}

class NoneNull {
  const NoneNull();
}